package yartiss.gui.graphics;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.swing.JComponent;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

import yartiss.engine.simulation.Event;
import yartiss.engine.simulation.IEventAddedListener;
import yartiss.engine.simulation.events.ActivateTaskEvent;
import yartiss.engine.simulation.events.CheckDeadlineEvent;
import yartiss.engine.simulation.events.CheckEffectiveDeadlineEvent;
import yartiss.engine.simulation.events.DeadlineMissedEvent;
import yartiss.engine.simulation.events.EnergyFailureEvent;
import yartiss.engine.simulation.events.EventVisitorAdapter;
import yartiss.engine.simulation.events.JobExecutionBeginEvent;
import yartiss.engine.simulation.events.JobExecutionEndEvent;
import yartiss.engine.simulation.system.ITask;
import yartiss.service.model.IEventsModel;

@SuppressWarnings("serial")
public class JTaskDiagram extends JComponent implements Scrollable, IScreenShot{

	private final RenderingHints   hints  = new RenderingHints(null);
	private IEventsModel evenmodeltsModel;
	private int xUnit = 10;
	private int yUnit = 10;
	private int margin = 50;
	private int xAlternate = 2;
	private Map<ITask,Color> tasksColor = new HashMap<ITask,Color>();
	/**
	 * @return the eventsModel
	 */
	public IEventsModel getEventsModel() {
		return evenmodeltsModel;
	}

	/**
	 * @param eventsModel the eventsModel to set
	 */
	public void setEventsModel(IEventsModel eventsModel) {
		unregisterListeners();
		this.evenmodeltsModel = eventsModel;
		int colorIndex=0;
		tasksColor.clear();
		if(evenmodeltsModel==null)return ;
		for (ITask task : evenmodeltsModel.getAllTasks()) {
			tasksColor.put(task,  generateColor(colorIndex));
			colorIndex++;
		}
		registerListeners();
	}

	/**
	 * 
	 * @param model
	 */
	public JTaskDiagram(IEventsModel model){
		this.setEventsModel(model);
		setOpaque(true);
		setBackground(Color.white);
	}


	/**
	 * 
	 */
	private IEventAddedListener eventListener = new IEventAddedListener() {
		@Override
		public void perfomredAction(Event e) {
			draw = true;
			repaint();
		}
	};
	
	private int offset = 0;
	private Point lastVpPos = new Point();
	
	private int translate(int x){
		if(getParent() instanceof JViewport){
			JViewport vp = (JViewport)getParent();
			Point pos = vp.getViewPosition();
			return  x - pos.x;
		}
		return x;
	}

	private BufferedImage image  = null;
	private boolean draw = false;
	@Override
	protected void paintComponent(Graphics g) {
		
		Rectangle size = g.getClipBounds();
		Dimension gsize = getSize();
		Point vpPos = lastVpPos;
		if(getParent() instanceof JViewport){
			JViewport vp = (JViewport)getParent();
			size = vp.getVisibleRect();
			vpPos = vp.getViewPosition();
		}
		if(lastVpPos.x != vpPos.x){
			offset += vpPos.x-lastVpPos.x;
			if(offset<0 || offset+size.width > image.getWidth()){
				draw = true;
				offset = 0;
			}
		}
		lastVpPos = new Point(vpPos);
		if(image ==null || image.getWidth() != size.width*3/2 || image.getHeight() != gsize.height){
			image = new BufferedImage(size.width*3/2, gsize.height, BufferedImage.TYPE_INT_ARGB);
			offset = 0;
			draw = true;
		}

		if(draw && evenmodeltsModel != null){
			Graphics2D g2 = (Graphics2D)image.getGraphics();
			hints.put( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			hints.put( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
			g2.setRenderingHints(hints);
			g2.setPaint(getBackground());
			g2.fillRect(0, 0, image.getWidth(), image.getHeight());
			g2.setPaint(Color.black);
			List<DrawJobsEventVisitor> visitors = new ArrayList<DrawJobsEventVisitor>();
			boolean ifOneJobNotYetFinished = false;
			long lastDate = 0;
			int y = margin+yUnit;
			for (ITask task : evenmodeltsModel.getAllTasks()) {
				List<Event> taskEvents = evenmodeltsModel.getAllTaskEvents(task);
				DrawJobsEventVisitor eventPaintVisitor = new DrawJobsEventVisitor(g2,tasksColor.get(task), y);
				visitors.add(eventPaintVisitor);
				for (Event event : taskEvents) {
					event.accept(eventPaintVisitor);
					if(event.getDate() > lastDate)lastDate = event.getDate();
				}
				if(eventPaintVisitor.ifBegin)ifOneJobNotYetFinished=true;
				paintLandMark(g2,translate(margin), y+yUnit, getPreferredSize().width-2*margin);
				drawString(g2, "t", translate(margin/2), y+yUnit/2);
				drawString(g2, task.getId()+"", translate(margin/2+3), y+yUnit/2+3);
				y += (yUnit+margin);
			}
			if(ifOneJobNotYetFinished){
				for (DrawJobsEventVisitor drawJobsEventVisitor : visitors) {
					drawJobsEventVisitor.drawEnd(lastDate);
				}
			}
			
			Dimension old = getPreferredSize();
			if(old.width!=2*margin+getXUnit()*lastDate || old.height!= y){
				old.width = (int) (2*margin+getXUnit()*lastDate+70);
				old.height =  y;
				setPreferredSize(old);
				draw = true;
				revalidate();
			}
			draw = false;
			g2.dispose();
		}
		super.paintComponent(g);
		g.drawImage(image, vpPos.x-offset, 0,image.getWidth(), image.getHeight(), null);
		g.dispose();
	}

	private void paintLandMark(Graphics2D g,int x0, int y0, int length){

		g.drawLine(x0, y0, x0+length, y0);
		int nb = length/getXUnit();
		for (int i = 0; i < nb; i++) {
			g.drawLine(x0+i*getXUnit(), y0+5, x0+i*getXUnit(),y0-5);
			if(i%getXAlternate()==0)
				drawString(g,i+"",x0+i*getXUnit(),y0+5);
		}
	}

	private  void drawString( Graphics2D g2, String str,int x, int y) {
		g2.setPaint(Color.black);
		FontMetrics metric = g2.getFontMetrics();
		int h = metric.getAscent();
		int n = str.length()>1 ? 2 : 4;
		g2.drawString(str,x-h/n,y+h);
	} 


	/**
	 * 
	 */
	private void registerListeners(){
		if(evenmodeltsModel != null)
			evenmodeltsModel.addEventAddedListener(eventListener);
	}

	/**
	 * 
	 */
	private void unregisterListeners(){
		if(evenmodeltsModel != null)
			evenmodeltsModel.removeEventAddedListener(eventListener);
	}

	class DrawJobsEventVisitor extends EventVisitorAdapter {
		public boolean ifBegin = false;
		long beginDate;
		private final Graphics2D g;
		private final int y;
		private final Color color;
		public DrawJobsEventVisitor(Graphics2D g,Color color, int y){
			this.g = g;
			this.color = color;
			this.y = y;
		}

		@Override
		public void visit(JobExecutionBeginEvent e) {
			ifBegin = true;
			beginDate = e.getDate();
		}
		@Override
		public void visit(JobExecutionEndEvent e) {
			drawEnd(e.getDate());
		}
		@Override
		public void visit(ActivateTaskEvent e) {
			int thikness = xUnit/5;
			int x = translate((int) (e.getDate()*getXUnit())+margin);
			
			int topY = y-yUnit/2;
			g.drawLine(x, topY, x, y+yUnit);
			g.fillPolygon(new int[]{x-thikness,x,x+thikness}, new int[]{topY,topY-thikness*2,topY}, 3);
		}

		@Override
		public void visit(CheckDeadlineEvent e) {
			int thikness = xUnit/5;
			int x = translate((int) (e.getDate()*getXUnit())+margin);
			int bottomY = y+yUnit-thikness*2;
			int topY = y-yUnit/2;
			g.drawLine(x, topY, x, y+yUnit);
			g.fillPolygon(new int[]{x-thikness,x,x+thikness}, new int[]{bottomY,bottomY+thikness*2,bottomY}, 3);
		}
		
		@Override
		public void visit(CheckEffectiveDeadlineEvent e) {
			Paint paint = g.getPaint();
			g.setColor(color);
			int thikness = xUnit/5;
			int x = translate((int) (e.getDate()*getXUnit())+margin);
			int bottomY = y+yUnit-thikness*2;
			int topY = y-yUnit/2;
			g.drawLine(x, topY, x, y+yUnit);
			g.fillPolygon(new int[]{x-thikness,x,x+thikness}, new int[]{bottomY,bottomY+thikness*2,bottomY}, 3);
			g.setPaint(paint);
		}


		@Override
		public void visit(DeadlineMissedEvent e) {
			drawEnd(e.getDate());
			drawEvent(e);
		}
		
		@Override
		public void visit(EnergyFailureEvent e) {
			drawEnd(e.getDate());
			drawEvent(e);
		}


		public void drawEvent(Event e){
			int x = translate((int) (e.getDate()*getXUnit())+margin);
			g.drawLine(x, y, x+yUnit/3,y-yUnit/3);
			drawString(g, e.toString(), x+yUnit/2, y-yUnit/2);
		}

		public void drawEnd(long date){
			if(!ifBegin) return;
			int width = (int) (getXUnit()*(date-beginDate));
			if(width>0){
				int x = translate((int) (beginDate*getXUnit())+margin);
				Paint p = g.getPaint();
				g.setPaint(color);
				g.fillRect(x, y, width, yUnit);
				g.setPaint(p);
				g.drawRect(x, y, width, yUnit);
			}
			ifBegin = false;
			beginDate = -1;
		}

		
	};

	private  static Color[] colors = new Color[]{Color.orange, Color.green, Color.red, Color.blue, Color.magenta, Color.yellow, Color.cyan, Color.gray, Color.pink, Color.darkGray};
	private static Color generateColor(int colorIndex){
		if(colorIndex < colors.length)return colors[colorIndex];
		Random r = new Random();
		return new Color(r.nextInt(200),r.nextInt(200),r.nextInt(200));
	}


	@Override
	public Dimension getPreferredScrollableViewportSize() {
		Dimension d = new Dimension(5000, 5000);
		return d;
	}


	@Override
	public int getScrollableBlockIncrement(Rectangle visibleRect,int orientation, int direction) {
		switch(orientation) {
		case SwingConstants.VERTICAL:
			return visibleRect.height;
		case SwingConstants.HORIZONTAL:
			return visibleRect.width;
		default:
			throw new IllegalArgumentException("Invalid orientation: " + orientation);
		}
	}


	@Override
	public boolean getScrollableTracksViewportHeight() {
		Container parent = SwingUtilities.getUnwrappedParent(this);
		if (parent instanceof JViewport) {
			return parent.getHeight() > getPreferredSize().height;
		}
		return false;
	}


	@Override
	public boolean getScrollableTracksViewportWidth() {
		Container parent = SwingUtilities.getUnwrappedParent(this);
		if (parent instanceof JViewport) {
			return parent.getWidth() > getPreferredSize().width;
		}
		return false;
	}


	@Override
	public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
		 switch(orientation) {
	        case SwingConstants.VERTICAL:
	            return visibleRect.height / 10;
	        case SwingConstants.HORIZONTAL:
	            return visibleRect.width / 10;
	        default:
	            throw new IllegalArgumentException("Invalid orientation: " + orientation);
	        }
	}


	public void setXUnit(int value) {
		xUnit = value;
		yUnit = value;
		draw = true;
		repaint();
		revalidate();
	}

	/**
	 * @return the xUnit
	 */
	public int getXUnit() {
		return xUnit;
	}
	
	
	public void setYUnit(int value) {
		yUnit = value;
		draw = true;
		repaint();
		revalidate();
	}

	/**
	 * @return the yUnit
	 */
	public int getYUnit() {
		return yUnit;
	}

	/**
	 * @return the xAlternate
	 */
	public int getXAlternate() {
		return xAlternate;
	}

	/**
	 * @param xAlternate the xAlternate to set
	 */
	public void setxAlternate(int xAlternate) {
		this.xAlternate = xAlternate;
		draw = true;
		repaint();
	}

	@Override
	public Image getScreenShot() {
		return image;
	}
	
	
}
